package BNS.coloring;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Label;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.rmi.RemoteException;
import java.util.Vector;

import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;

import BNS.graphs.ColorMap;
import BNS.graphs.Graph;
import BNS.graphs.GraphPanel;
import BNS.graphs.Node;
import BNS.plumbing.BNSPersona;
import BNS.plumbing.ClientLiaison;
import BNS.plumbing.ClientLiaisonInterface;

/** The applet GUI that presents to the player of ColoringGame. */
public class ColoringPersona extends BNSPersona implements ActionListener {
	private ClientLiaisonInterface game;
	private Graph graphlet;
	private JButton[] colorButton;
	private JComponent panel;
	private GraphPanel graphPanel;
	private int chroma, timeLimit, numEdges;
	private JProgressBar progressBar, timeBar;
	private JLabel messageBar, dynamicLabel; // Note messageBar is only created when showProgBar is true.
	private PatchIcon winIcon;
	private final ColorMap order, render;
	private boolean showNodes;
	private boolean showProgBar;
	private boolean showTimeBar;
	private final String payoffDefinition;// , payoffDefinition2;
	private JLabel movePenaltyBar;
	private float costToMove;
	private String movePenaltyMsg; // Message displaying how much the player has lost by moving.
	private final Vector<Float> bonus;

	// private final int specialColor;

	/**
	 * This constructor just calls its superclass's constructor, with all the
	 * same parameters. It is intended to be called on the *server machine*, so
	 * that the 5 arguments here are stored in it; later the resulting object is
	 * passed to the client where further setup is performed.
	 * 
	 * @param g
	 *            the (remote) game being played
	 * @param id
	 *            the clientID
	 * @param gr
	 *            the graphlet for this player
	 * @param cr
	 *            the number of colors to be used in this game
	 * @param tl
	 *            the maximum number of seconds this game will be allowed to run
	 * @param ne
	 *            the total number of edges in the master graph
	 * @param colormapIsIdentity
	 *            flag indicating whether to use true colors to decorate nodes;
	 *            if false, will use randomized colors instead
	 * @param showNodes
	 *            flag indicating whether to hide the node colors until a move
	 *            is made
	 * @param defn
	 *            the definition of rewards for success and failure
	 * @param movePenaltyMsg
	 * 			  Tells the player how much he or she has lost by moving.
	 * @param costToMove
	 * 			  The amount it costs the player to assign or change color.
	 * @param payOffStr
	 *            an array of formatted dollar amounts for the various outcomes
	 *            (yuck!)
	 */
	public ColoringPersona(ClientLiaison g, Graph gr, int cr, int tl, int ne,
			boolean colormapIsIdentity, boolean showNodes, String defn, String movePenaltyMsg,
			float costToMove, String[] payOffStr, Vector<Float> bo, boolean showProg,
			boolean showTime) {
		game = g;
		graphlet = gr;
		chroma = cr;
		timeLimit = tl;
		numEdges = ne;
		order = colormapIsIdentity ? ColorMap.identity() : ColorMap.scramble(chroma);
		render = colormapIsIdentity ? ColorMap.identity() : ColorMap.scramble(GraphPanel.swatches.length - 1);
		this.showNodes = showNodes;
		bonus = bo;
		// specialColor=sp;
		payoffDefinition = defn;
		this.movePenaltyMsg = movePenaltyMsg;
		this.costToMove = costToMove;
		/*
		 * payoffDefinition2= specialColor<=0?null:
		 * GraphPanel.swatches[render.map(sp)].name +
		 * " you will earn "+payOffStr
		 * [1]+", otherwise you will earn "+payOffStr[0]+".";
		 */
		showProgBar = showProg;
		showTimeBar = showTime;
	}

	/**
	 * returns the Node that is the eigennode for this graphlet. If the graph is
	 * the mastergraph, then this returns null.
	 */
	public Node getEigenNode() {
		return graphlet.eigenNode;
	}

	public JComponent startPanel() {
		panel = new JPanel();
		panel.setLayout(new BorderLayout());
		// panel.setLayout(new BoxLayout(panel,BoxLayout.Y_AXIS));

		JPanel statusPanel = new JPanel();

		GridBagLayout gridbag = new GridBagLayout();
		GridBagConstraints constraints = new GridBagConstraints();
		constraints.fill = GridBagConstraints.BOTH;

		// g.setFont(new Font("SansSerif", Font.PLAIN, 14));
		statusPanel.setLayout(gridbag);

		if (showProgBar) {
			constraints.weightx = 1.0; // Comment out this line to center the progress bar and status text.
			JLabel label1 = new JLabel("progress: ", JLabel.RIGHT);
			constraints.gridwidth = GridBagConstraints.RELATIVE;
			gridbag.setConstraints(label1, constraints);
			statusPanel.add(label1);
			
			progressBar = new JProgressBar(0, numEdges);
			progressBar.setValue(0);
			progressBar.setStringPainted(true);
			progressBar.setAlignmentX(Component.CENTER_ALIGNMENT);
			progressBar.setAlignmentY(Component.CENTER_ALIGNMENT);
			constraints.gridwidth = GridBagConstraints.REMAINDER; // end row
			gridbag.setConstraints(progressBar, constraints);
			statusPanel.add(progressBar);

			JLabel label2 = new JLabel("status: ", JLabel.RIGHT);
			constraints.gridwidth = GridBagConstraints.RELATIVE;
			gridbag.setConstraints(label2, constraints);
			statusPanel.add(label2);
			
			messageBar = new JLabel("persona born", JLabel.CENTER);
			messageBar.setAlignmentX(Component.CENTER_ALIGNMENT);
			messageBar.setAlignmentY(Component.CENTER_ALIGNMENT);
			constraints.gridwidth = GridBagConstraints.REMAINDER; // end row
			gridbag.setConstraints(messageBar, constraints);
			statusPanel.add(messageBar);
		}

		if (showTimeBar) {
			constraints.weightx = 1.0; // Comment out this line to center the time bar.
			JLabel label3 = new JLabel("elapsed time: ", JLabel.RIGHT);
			constraints.fill = GridBagConstraints.BOTH;
			constraints.gridwidth = GridBagConstraints.RELATIVE;
			gridbag.setConstraints(label3, constraints);
			statusPanel.add(label3);
			
			timeBar = new JProgressBar(0, timeLimit * 1000);
			timeBar.setValue(0);
			timeBar.setAlignmentX(Component.CENTER_ALIGNMENT);
			timeBar.setAlignmentY(Component.CENTER_ALIGNMENT);
			constraints.gridwidth = GridBagConstraints.REMAINDER; // end row
			gridbag.setConstraints(timeBar, constraints);
			statusPanel.add(timeBar);
		}

		/*
		 * This section was used in games with independent payoffs JLabel label4=
		 * new JLabel("your current payoff:",JLabel.RIGHT);
		 * constraints.gridwidth= GridBagConstraints.RELATIVE;
		 * gridbag.setConstraints(label4, constraints); statusPanel.add(label4);
		 * dynamicLabel = new JLabel(winIcon); constraints.gridwidth=
		 * GridBagConstraints.REMAINDER; //end row
		 * gridbag.setConstraints(dynamicLabel, constraints);
		 * statusPanel.add(dynamicLabel);
		 */
		JLabel newl1 = new JLabel(" ", JLabel.CENTER);
		gridbag.setConstraints(newl1, constraints);
		statusPanel.add(newl1);

		JLabel defnBar = new JLabel(payoffDefinition, JLabel.CENTER);
		defnBar.setAlignmentX(Component.CENTER_ALIGNMENT);
		defnBar.setAlignmentY(Component.CENTER_ALIGNMENT);
		constraints.gridwidth = GridBagConstraints.REMAINDER; // end row
		gridbag.setConstraints(defnBar, constraints);
		statusPanel.add(defnBar);
		
		
		// Display cost to move penalty string.
		if (costToMove != 0)
		{			
			movePenaltyBar = new JLabel(movePenaltyMsg, JLabel.CENTER);
			movePenaltyBar.setAlignmentX(Component.CENTER_ALIGNMENT);
			constraints.gridwidth = GridBagConstraints.REMAINDER; // end row
			gridbag.setConstraints(movePenaltyBar, constraints);
			statusPanel.add(movePenaltyBar);
		}

		if (bonus.size() > 0) {
			JLabel newl = new JLabel(" ", JLabel.CENTER);
			gridbag.setConstraints(newl, constraints);
			statusPanel.add(newl);

			JLabel defn2Bar = new JLabel("Additionally, you will earn a bonus for "
				+ "the color you have chosen when the session ends successfully.",
				JLabel.CENTER);
			gridbag.setConstraints(defn2Bar, constraints);
			statusPanel.add(defn2Bar);

			JLabel defn3Bar = new JLabel("The bonuses are listed below.", JLabel.CENTER);
			gridbag.setConstraints(defn3Bar, constraints);
			statusPanel.add(defn3Bar);

			/*JLabel newl2 = new JLabel(" ", JLabel.CENTER);
			gridbag.setConstraints(newl2, constraints);
			statusPanel.add(newl2);*/

			// JLabel payOffTable;
			// String row;
			String bonusRow = "<html><div align=\"center\"><table border=\"0\"" +
				" cellpadding=\"15\"><tr>";
			for (int i = 0; i < chroma; i++) {
				/*JLabel current = new JLabel(GraphPanel.swatches[render.map(i + 1)].name + ": "
						+ String.format("$%.2f", bonus.size() > i ? bonus.get(i) : 0) + "     ");
				constraints.gridwidth = chroma;
				gridbag.setConstraints (current, constraints);
				statusPanel.add(current);*/
				String currentItem = GraphPanel.swatches[render.map(i + 1)].name + ": "
					+ String.format("$%.2f", bonus.size() > i ? bonus.get(i) : 0); 
				bonusRow += "<td>" + currentItem + "</td>" ;
			}
			bonusRow += "</tr></table></div></html>";
			JLabel payOffTable = new JLabel (bonusRow, JLabel.CENTER);
			gridbag.setConstraints(payOffTable, constraints);
			statusPanel.add(payOffTable);
		}

		panel.add("North", statusPanel);
		statusPanel.setMaximumSize(new Dimension(450, 400)); // ANY limit seems
																// to tame this
		
		graphPanel = new GraphPanel(render);
		graphPanel.setDisplayNodes(showNodes);
		// graphPanel.setSize(500,200);
		// graphPanel.setMinimumSize(new Dimension(500,500));
		panel.add("Center", graphPanel);
		graphPanel.init(graphlet);

		JPanel colorPanel = new JPanel(new FlowLayout());
		colorPanel.add(new Label("your color:"));
		colorButton = new JButton[chroma];
		for (int i = 0; i < chroma; i++) {
			// System.err.println("startPanel "+ i
			// +" "+GraphPanel.swatches[i+1].name);
			colorButton[i] = new JButton(GraphPanel.swatches[render.map(order.map(i + 1))].name);
			// colorButton[i].setForeground(GraphPanel.swatches[render.map(order.map(i+1))].color);
			colorButton[i].setBackground(GraphPanel.swatches[render.map(order.map(i + 1))].color);
			colorButton[i].addActionListener(this);
			colorPanel.add(colorButton[i]);
		}
		// colorPanel.setMaximumSize(new Dimension(500,400)); // ANY limit seems
		// to overconstrain this
		// colorPanel.setMinimumSize(new Dimension(500,200));
		colorPanel.setPreferredSize(new Dimension(500, chroma > 5 ? 75 : 45));
		panel.add("South", colorPanel);

		return panel;
	}

	public JComponent killPanel() {
		return panel;
	}

	/** mandated by ActionListener; handles coloring buttons */
	public void actionPerformed(ActionEvent e) {
		Object src = e.getSource();
		// System.err.println("actionPerformed "+ src
		// +"{"+bYEL+","+bRED+","+bGRN+"}");
		int but = 0;
		while (src != colorButton[but])
			but++;
		try {
			if (game.alter(new Integer(order.map(but + 1))))
				graphPanel.setDisplayNodes(true);
		} catch (RemoteException ex) {
			System.err.println(ex);
		}
	}

	public void gameUpdate(java.io.Serializable dataBlob) {
		assert (dataBlob instanceof BNS.coloring.Envelope);
		BNS.coloring.Envelope envelope = (BNS.coloring.Envelope) dataBlob;
		
		if (showProgBar) //  Otherwise, messageBar is null, and there's no message to set.
			setMessage(envelope.status);
		// statusPanel.setWinStatus(envelope.localStates.winStatus);
		
		if (showTimeBar)
			timerUpdate(envelope.elapsedTime);
		if (showProgBar)
			progressUpdate(envelope.currentProgress);
		
		// statusPanel.setPay(envelope.local.payRate,
		// envelope.local.cumulativePay);

		/*
		 * int[] state= (int[])dataBlob; //System.err.print("client "+ myID
		 * +" gameUpdate: "); Enumeration eno= graphlet.enumNodes(); while
		 * (eno.hasMoreElements()) { Node n= (Node)eno.nextElement(); int nidx=
		 * n.getIndex(); n.color= nhbrs[nidx]; //System.err.print(" "+ n.lbl
		 * +" ["+ nidx +"]  value "+ n.color); }
		 */
		NodeState[] nhbrs = envelope.localStates;
		for (int nsi = 0; nsi < nhbrs.length; nsi++) {
			Node n = graphlet.getNode(nhbrs[nsi].index);
			n.color = nhbrs[nsi].colorInt;
		}
		
		// Update running cost to move total and display it to the user.
		if (costToMove != 0)
		{
			updateMovePenaltyMsg (nhbrs);
			movePenaltyBar.setText(movePenaltyMsg);
			movePenaltyBar.repaint();
		}
		
		// System.err.println();
		graphPanel.repaint();
	}

	/** called to update the display of elapsed game time */
	public void timerUpdate(long elapsedTime) {
		timeBar.setValue((int) elapsedTime);
	}

	/** called to update the display of current overall "social welfare" */
	public void progressUpdate(int progress) {
		progressBar.setValue(progress);
	}
	
	/**
	 * Updates the movePenaltyMsg.
	 * @param envelope
	 */
	public void updateMovePenaltyMsg (NodeState[] nodeStates)
	{
		// Note that the eigennode is always nodeStates[0].
		int numColorChanges = nodeStates[0].numColorChanges;
		String loss = String.format("$%.2f", numColorChanges * costToMove);
		
		if (costToMove == 0) return;
		else {
			movePenaltyMsg = "You have changed colors " + numColorChanges + " time";
			if (numColorChanges != 1)
				movePenaltyMsg += "s";
			movePenaltyMsg += ", costing " + loss + ".";
		}
	}

	/** called to update the display of an incidental status message to player */
	public void setMessage(String message) {
		if (message == null)
			return;

		if (messageBar == null) {
			System.err.println("ERROR: messageBar is null in setMessage() -- ColoringPersona.java");
			return;
		}
		
		messageBar.setText(message);
		Toolkit.getDefaultToolkit().beep();
	}

	/** called to update the indicator of local well-coloredness */
	public void setWinStatus(int status) {
		winIcon.setStatus(status);
		dynamicLabel.repaint();
	}

	public int getCycleTime() {
		return 200;
	}

	public String getTitle() {
		return "ColoringPersona 1 (client " + game + ")";
	}

	/** A little icon class that displays the current winStatus value. */
	class PatchIcon implements Icon, java.io.Serializable {
		/**
     * 
     */
		private static final long serialVersionUID = 1L;
		private int wide, nPatches, patch, strhigh, status;
		private final String[] lbls;

		public int getIconWidth() {
			return wide;
		}

		public int getIconHeight() {
			return 18;
		}

		public PatchIcon(int wd, int fontHigh, String[] lbls) {
			wide = wd;
			this.lbls = lbls;
			nPatches = lbls.length;
			patch = wide / nPatches;
			strhigh = (fontHigh + getIconHeight()) / 2;
		}

		public void setStatus(int stat) {
			status = stat;
		}

		public void paintIcon(Component c, Graphics g, int x, int y) {
			int horz = x + patch * status;
			// System.err.println("paintIcon "+horz+","+ y);
			g.fill3DRect(horz, y, patch, getIconHeight(), true);
			g.setColor(Color.WHITE);
			int strwide = g.getFontMetrics().stringWidth(lbls[status]);
			g.drawString(lbls[status], horz + (patch - strwide) / 2, y
					+ strhigh);
		}
	};

} // EOC ColoringPersona
